﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace ArgusLib.InteropServices
{
	public static class Marshaler
	{
		public delegate T MarshalHandler<T>(ref IntPtr ptr);
		public delegate bool ComparisonHandler<T>(T t1, T t2);

		static bool IsStructure(Type type)
		{
			if (type.IsValueType == true)
				return true;
			
			object[] attr = type.GetCustomAttributes(typeof(StructLayoutAttribute), false);
			if (attr == null || attr.Length < 1)
				return false;

			return true;
		}

		public static void Copy(IntPtr source, IntPtr target, int count)
		{
			for (int i = 0; i < count; i++)
			{
				IntPtr s = new IntPtr(source.ToInt64() + i);
				byte b = Marshal.ReadByte(s);
				IntPtr t = new IntPtr(target.ToInt64() + i);
				Marshal.WriteByte(t, b);
			}
		}

		public static T[] MarshalArray<T>(IntPtr ptr, T endOfArray)
		{
			ComparisonHandler<T> comparisonHandler = new ComparisonHandler<T>((t1, t2) =>
				{
					return t1.Equals(t2);
				});
			return Marshaler.MarshalArray<T>(ptr, endOfArray, comparisonHandler);
		}

		public static T[] MarshalArray<T>(IntPtr ptr, T endOfArray, ComparisonHandler<T> comparisonHandler)
		{
			if (Marshaler.IsStructure(typeof(T)) == false)
				throw new GenericTypeParameterNotSupportetException("T must be a structure.");

			MarshalHandler<T> marshalHandler;
			if (typeof(T).IsEnum == true)
				marshalHandler = new MarshalHandler<T>(Marshaler.MarshalArray_Enums<T>);
			else
				marshalHandler = new MarshalHandler<T>(Marshaler.MarshalArray_Structures<T>);

			return Marshaler.MarshalArray<T>(ptr, endOfArray, marshalHandler, comparisonHandler);
		}

		private static T MarshalArray_Structures<T>(ref IntPtr ptr)
		{
			int size = Marshal.SizeOf(typeof(T));
			T RetVal = (T)Marshal.PtrToStructure(ptr, typeof(T));
			ptr = new IntPtr(ptr.ToInt64() + size);
			return RetVal;
		}

		private static T MarshalArray_Enums<T>(ref IntPtr ptr)
		{
			Type type = typeof(T);
			type = Enum.GetUnderlyingType(type);
			int size = Marshal.SizeOf(type);
			T RetVal = (T)Marshal.PtrToStructure(ptr, type);
			ptr = new IntPtr(ptr.ToInt64() + size);
			return RetVal;
		}

		public static T[] MarshalArray<T>(IntPtr ptr, T endOfArray, MarshalHandler<T> marshalHandler, ComparisonHandler<T> comparisonHandler)
		{
			if (ptr == IntPtr.Zero)
				return null;

			List<T> RetVal = new List<T>();
			T element = marshalHandler(ref ptr);
			while (comparisonHandler(element, endOfArray) == false)
			{
				RetVal.Add(element);
				element = marshalHandler(ref ptr);
			}
			return RetVal.ToArray();
		}
	}
}
